/*
	Copyright (c) 2002, Jonathan Feinberg
	All rights reserved.
	
	Redistribution and use in source and binary forms, with or without
	modification, are permitted provided that the following conditions are
	met:
	
	*	Redistributions of source code must retain the above copyright
		notice, this list of conditions and the following disclaimer.
	*	Redistributions in binary form must reproduce the above copyright
		notice, this list of conditions and the following disclaimer in the
		documentation and/or other materials provided with the distribution.
	*	The name of the author may not be used to endorse or promote
		products derived from this software without specific prior written
		permission.
	
	
	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
	IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
	TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
	PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
	OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
	EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
	PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
	PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
	LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
	NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
	SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

    $Id: KeepSoundAwake.mm,v 1.3 2002/04/07 17:28:32 jdf Exp $

*/

#include <Foundation/Foundation.h>

// For CFRunLoop stuff
#include <CoreFoundation/CoreFoundation.h>

// For ApplicationEventLoop stuff
#include <Carbon/Carbon.h>

// For NSSound
#include <AppKit/AppKit.h>

// For detecting whether the charger is plugged in
#include <IOKit/pwr_mgt/IOPM.h>
#include <IOKit/pwr_mgt/IOPMLib.h>

// For retrieving preferences
#include "KSAPrefConstants.h"

// For debug and status messages
#include <syslog.h>


// Globals used in determining whether we're running on battery
mach_port_t masterPort;
NSString *flagsString;

// We keep things that might be affected by pref changes in these globals
CFRunLoopTimerRef soundTimer;
BOOL shouldWatchCharger;

// The sound to play periodically
NSSound *sound;

// Are we running on battery power?
// Thanks to Paul Haddad (paul@pth.com) for this code, and for the notion.
int chargerIsConnected()
{
    NSMutableArray *array;
    int newFlags;

    if (kIOReturnSuccess == IOPMCopyBatteryInfo(masterPort, (CFArrayRef *)&array)) 
    {
        newFlags = [[[array lastObject] objectForKey:flagsString] intValue];
        [array release];
    } 
    else
        newFlags = kIOBatteryChargerConnect;
    return (newFlags & kIOBatteryChargerConnect);
}

// Called periodically by soundTimer
void soundTimerCallback(CFRunLoopTimerRef timer, void *info)
{
    if ((!shouldWatchCharger) || chargerIsConnected())
        [sound play];
}

// Called indirectly by DoQuitApp, below.
void delayedQuit(CFRunLoopTimerRef timer, void *info)
{
    QuitApplicationEventLoop();
}

/*
    This handler is installed to handle the "QUIT" AppleEvent
    It sets up a delayed call to "delayedQuit", above.  If we
    do not delay the QuitApplicationEventLoop() call, then the
    AppleScript infrastructure gets confused, and may wind up
    starting and restarting this app, just to tell it to quit!
*/
static pascal OSErr DoQuitApp(const AppleEvent *message, 
                              AppleEvent *reply, 
                              long refCon)
{

	CFRunLoopTimerRef qtimer = CFRunLoopTimerCreate( 
								  kCFAllocatorDefault,
		                          CFAbsoluteTimeGetCurrent() + 1,
		                          0,
		                          0,
		                          0,
		                          delayedQuit,
		                          NULL 
		                          );
	CFRunLoopAddTimer( CFRunLoopGetCurrent(), qtimer, kCFRunLoopCommonModes );

    return noErr;
}

void setupSoundTimer(int period)
{
    if (soundTimer)
    {
        CFRunLoopRemoveTimer( CFRunLoopGetCurrent(), soundTimer, kCFRunLoopCommonModes );
        CFRelease(soundTimer);
        soundTimer = NULL;
    }   
	soundTimer = CFRunLoopTimerCreate( 
								  kCFAllocatorDefault,
		                          CFAbsoluteTimeGetCurrent(),
		                          period,
		                          0,
		                          0,
		                          soundTimerCallback,
		                          NULL 
		                          );
	CFRunLoopAddTimer( CFRunLoopGetCurrent(), soundTimer, kCFRunLoopCommonModes );
}

void refreshPrefs()
{
    CFPreferencesAppSynchronize((CFStringRef)suiteID);

    NSString *s_period = 
            (NSString *)CFPreferencesCopyAppValue((CFStringRef)MFKSASeconds, 
                                                  (CFStringRef)suiteID);
    int period = s_period ? [s_period intValue] : 20;
    [s_period release];

    NSString *s_chargerAwareness = 
            (NSString *)CFPreferencesCopyAppValue((CFStringRef)MFKSAWatchCharger, 
                                                  (CFStringRef)suiteID);
    shouldWatchCharger = s_chargerAwareness ? [s_chargerAwareness intValue] : YES;
    [s_chargerAwareness release];

    setupSoundTimer(period);    
}

// This routine is called when the preferences pane wants to tell
// us that something has changed.  It simply calls refreshPrefs().
void prefsChanged(CFNotificationCenterRef center, void *observer,
            CFStringRef notificationName, const void *observedObject,
            CFDictionaryRef userInfo)
{
    refreshPrefs(); 
}

int main (int argc, const char * argv[])
{
    // Set up battery detection globals
    IOMasterPort( MACH_PORT_NULL, &masterPort );
    flagsString = [[NSString alloc] initWithCString:kIOBatteryFlagsKey];
    
	// Compile with NOISY defined to make KeepSoundAwake noisy instead of silent
	// for debugging porpoises
#ifdef NOISY
    sound = 
    	[[NSSound alloc] initWithContentsOfFile: @"/System/Library/Sounds/Temple.aiff" 
    	                 byReference: NO];
#else
	// Determined from a hex dump of a "blank" AIFF sound file. 
	char AIFFbytes[54] = { 
		'F', 'O', 'R', 'M', 0x00, 0x00, 0x00, 0x2E, 
		'A', 'I', 'F', 'F', 'C', 'O', 'M', 'M',
		0x00, 0x00, 0x00, 0x12, 0x00, 0x02, 0x00, 0x00, 
		0x00, 0x00, 0x00, 0x10, 0x40, 0x0E, 0xAC, 0x44,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 'S', 'S', 'N', 'D', 
		0x00, 0x00, 0x00, 0x08, 
		0x00, 0x00,	0x00, 0x00, 0x00, 0x00, 0x00, 0x00
	};

    NSData *data = [[NSData alloc] initWithBytes:AIFFbytes length:54];
    sound = [[NSSound alloc] initWithData:data];
    [data release];
#endif

    // Get our preferences and start the sound timer
    refreshPrefs();

    // Listen for notification from the preference pane
    void *observer;  // an arbitrary pointer, ignored
    CFNotificationCenterAddObserver(CFNotificationCenterGetDistributedCenter(), 
                                    observer, 
                                    prefsChanged,
                                    (CFStringRef)MFKSAPrefsChangedNotification, 
                                    (CFStringRef)suiteID,
                                    CFNotificationSuspensionBehaviorDeliverImmediately);

    // Intercept "quit" Apple Event
    AEInstallEventHandler(kCoreEventClass, kAEQuitApplication,
            NewAEEventHandlerUPP(DoQuitApp), 0, FALSE);

	syslog(1, "starting up");
    RunApplicationEventLoop();
    
	syslog(1, "cleaning up and exiting");
    /* No fancy cleanup needed. */
    
    return 0;
}
